Introduction

In this course work I will explore dataset about students’ performance based on their habits and lifestyle. I have chosen this dataset, because it is rich on different discrete, continious variables with a good range and I assume good variance, which will be very useful in Data Science 2 class.

This dataset consists of 16 columns which could be categorized into these groups: - discrete variables (example: exercise frequency per week) - continious variables (example: sleep quantity) - categorical variables (example par time job, yes or no)

This is regression problem, because I will try to predict the student’s score for an exam, based on different variables.

Description

  • Collect your dataset(s), explore your data for deficiencies such as missing data and formatting problems and prepare it for modelling.
  • Extensive data collection and preparation yields extra credit but is not mandatory for this coursework.
  • Explore the data via descriptive statistics and visualization.

Collection

Here I would like to collect, prepare, and explore my data. First thing is to import the data set.

dt_students <- fread(file = "./data/student_habits_performance.csv")


I would like to check, if i have some nullish data in my dataset. I think it is a good idea to go through all rows and colums and check, if there is a NA. I want to check it with built-in function in R complete.cases(data_table). This function returns TRUE or FALSE if row contains a NA value.

That looks great. Now we can move on to exploration. But before I start, It is crucial to install all needed libraries.

library(data.table)
library(ggcorrplot)
library(ggExtra)
library(ggplot2)
library(ggridges)
library(ggsci)
library(ggthemes)
library(RColorBrewer)
library(svglite)
library(viridis)
library(scales)
library(rpart)
library(rpart.plot)
library(factoextra)

Exploration

I found some helpful functions in R, so we could have a look on our data. We will start with a structure, than we will get some statistic data and take a head() of the data

str(dt_students)
Classes ‘data.table’ and 'data.frame':  1000 obs. of  16 variables:
 $ student_id                   : chr  "S1000" "S1001" "S1002" "S1003" ...
 $ age                          : int  23 20 21 23 19 24 21 21 23 18 ...
 $ gender                       : chr  "Female" "Female" "Male" "Female" ...
 $ study_hours_per_day          : num  0 6.9 1.4 1 5 7.2 5.6 4.3 4.4 4.8 ...
 $ social_media_hours           : num  1.2 2.8 3.1 3.9 4.4 1.3 1.5 1 2.2 3.1 ...
 $ netflix_hours                : num  1.1 2.3 1.3 1 0.5 0 1.4 2 1.7 1.3 ...
 $ part_time_job                : chr  "No" "No" "No" "No" ...
 $ attendance_percentage        : num  85 97.3 94.8 71 90.9 82.9 85.8 77.7 100 95.4 ...
 $ sleep_hours                  : num  8 4.6 8 9.2 4.9 7.4 6.5 4.6 7.1 7.5 ...
 $ diet_quality                 : chr  "Fair" "Good" "Poor" "Poor" ...
 $ exercise_frequency           : int  6 6 1 4 3 1 2 0 3 5 ...
 $ parental_education_level     : chr  "Master" "High School" "High School" "Master" ...
 $ internet_quality             : chr  "Average" "Average" "Poor" "Good" ...
 $ mental_health_rating         : int  8 8 1 1 1 4 4 8 1 10 ...
 $ extracurricular_participation: chr  "Yes" "No" "No" "Yes" ...
 $ exam_score                   : num  56.2 100 34.3 26.8 66.4 100 89.8 72.6 78.9 100 ...
 - attr(*, ".internal.selfref")=<externalptr> 


Statistic data:

summary(dt_students)
  student_id             age           gender          study_hours_per_day social_media_hours netflix_hours   part_time_job      attendance_percentage  sleep_hours    diet_quality       exercise_frequency parental_education_level internet_quality   mental_health_rating
 Length:1000        Min.   :17.00   Length:1000        Min.   :0.00        Min.   :0.000      Min.   :0.000   Length:1000        Min.   : 56.00        Min.   : 3.20   Length:1000        Min.   :0.000      Length:1000              Length:1000        Min.   : 1.000      
 Class :character   1st Qu.:18.75   Class :character   1st Qu.:2.60        1st Qu.:1.700      1st Qu.:1.000   Class :character   1st Qu.: 78.00        1st Qu.: 5.60   Class :character   1st Qu.:1.000      Class :character         Class :character   1st Qu.: 3.000      
 Mode  :character   Median :20.00   Mode  :character   Median :3.50        Median :2.500      Median :1.800   Mode  :character   Median : 84.40        Median : 6.50   Mode  :character   Median :3.000      Mode  :character         Mode  :character   Median : 5.000      
                    Mean   :20.50                      Mean   :3.55        Mean   :2.506      Mean   :1.820                      Mean   : 84.13        Mean   : 6.47                      Mean   :3.042                                                  Mean   : 5.438      
                    3rd Qu.:23.00                      3rd Qu.:4.50        3rd Qu.:3.300      3rd Qu.:2.525                      3rd Qu.: 91.03        3rd Qu.: 7.30                      3rd Qu.:5.000                                                  3rd Qu.: 8.000      
                    Max.   :24.00                      Max.   :8.30        Max.   :7.200      Max.   :5.400                      Max.   :100.00        Max.   :10.00                      Max.   :6.000                                                  Max.   :10.000      
 extracurricular_participation   exam_score    
 Length:1000                   Min.   : 18.40  
 Class :character              1st Qu.: 58.48  
 Mode  :character              Median : 70.50  
                               Mean   : 69.60  
                               3rd Qu.: 81.33  
                               Max.   :100.00  


and this is a sample of dataset:

head(dt_houses)

I would like to start from density of a main values, which are from my domain knowledge are important for the best performance at the university.

density:

ggplot(data = dt_students, aes(x = attendance_percentage)) + 
  geom_density(fill="#f1b147", color="#f1b147", alpha=0.25) + 
  labs(
    x = 'Price',
    y = 'Density'
  ) +
  geom_vline(xintercept = mean(dt_students$attendance_percentage), linetype="dashed") + 
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

This density plot visualizes the distribution of the attendance percentage, showing that most students attend classes at a rate of ~85% roughly, and this is a right-skewed distribution. The dashed vertical line represents the mean attendance percentage (~84-85%). The plot shows, that the majority of students are attending most of the classes.

Area density:

ggplot(data = dt_houses, aes(x = area)) + 
  geom_density(fill="#f1b147", color="#f1b147", alpha=0.25) + 
  labs(
    x = 'Price',
    y = 'Density'
  ) +
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

The area density plot looks similar to price density plot and can also make sense, because if house has a bigger area, the higher cost is quite expected. This plot shows that most houses are having area in range ~3000-5000. But some properties have area more than 12000.


Next plot will visualize the distribution of price depending on area.

ggplot() + 
  geom_point(data = dt_houses, aes(x = area, y = price, color = parking)) +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = "M")) + 
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

This scatter plot visualizes the relationship between house area (x-axis) and price (y-axis), with color indicating the number of parking spaces. It shows a positive correlation between area and price—larger houses tend to be more expensive. However, there is some variability, as some large houses have relatively lower prices. The color gradient suggests that houses with more parking spaces (lighter blue) tend to be higher in price and larger in area.

The next plot, which I am going to do is a boxplot and I want to use bedrooms as a factor variable on x axis and price on y-axis, to get an overall understanding, how amount of bedrooms affect price.

ggplot(data = dt_houses, aes(x = factor(bedrooms), y = price)) +
  geom_boxplot() + 
  theme_minimal() 

Boxplot shows, that on average, houses with more bedrooms have higher prices, but around 4-6 bedrooms, 1 quantile stagnates, and so does median price. There are some outliers, but not too much.

It is also interesting to take a look at distribution of bedrooms, so next plot would be a histogram, because I want to know, which amount of bedrooms is the most “popular” in the whole dataset.

ggplot(data = dt_houses, aes(x = bedrooms)) + 
  geom_histogram(fill="#2f9e44", color="#2f9e44", alpha=0.25) + 
  geom_vline(xintercept = mean(dt_houses$bedrooms), linetype="dashed") + 
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

mean of the bedrooms:

mean(dt_houses$bedrooms)
[1] 2.965138

From this visualization we can mention, that the most of the houses have 2, 3 or 4 rooms. 1, 5 and 6 rooms are not as popular in this dataset.

Let’s have a look at histogram of stories:

ggplot(data = dt_houses, aes(x = stories)) + 
  geom_histogram(fill="#2f9e44", color="#2f9e44", alpha=0.25) + 
  geom_vline(xintercept = mean(dt_houses$stories), linetype="dashed") + 
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

mean(dt_houses$stories)
[1] 1.805505

This plot shows that most popular amount of stories are 1 and 2. 3 and 4 makeing less than 100 houses together.

Bathrooms are also interesting variable, so let’s take a look at histogram and a Boxplot bathrooms and price:

ggplot(data = dt_houses, aes(x = bathrooms)) + 
  geom_histogram(fill="#2f9e44", color="#2f9e44", alpha=0.25) + 
  geom_vline(xintercept = mean(dt_houses$bathrooms), linetype="dashed") + 
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

ggplot(data = dt_houses, aes(x = factor(bathrooms), y = price)) +
  geom_boxplot() + 
  theme_minimal() 

here it is also almost obvious, that, if we have more bathrooms, price will be also up. Only one disadvantage, that in my dataset I do not have enough data about properties with 3 or 4 bathrooms, I have some on 3, but really luck on 4.

Furnishing is also important, many people search for apartments with furniture, but furniture could be not in a best shape or buyer may do not like the style. So from my opinion, it is not as strong(in prediction), as for example area.

How much real estate furnished or not:

ggplot(data = dt_houses, aes(x = factor(furnishingstatus), fill = factor(furnishingstatus))) + 
  geom_bar(color="#ced4da", alpha=0.25) + 
  scale_fill_viridis_d(option = "D") + 
  labs(title = "Bar Chart with Different Colors", 
       x = "Furnishing Status", 
       y = "Count") + 
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

We can see, that most of the houses are semi-furnished. which is also logical, because when we sell a house or apartment, probably we would take in most of the cases the most valuable things for us and furniture included.

Now, it would be great, to look at price and area distribution in differently furnished properties

ggplot(data = dt_houses, aes(y = price, x = area)) + 
  geom_point(data = dt_houses, aes(y = price, x = area, color = bedrooms)) +
  geom_hline(yintercept = mean(dt_houses$price), linetype='dashed') + 
  facet_grid(.~furnishingstatus) +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = "M")) +
  scale_color_distiller(type = "seq", palette = "Greens") +
  theme_minimal() + 
  theme(axis.line = element_line(color = "#000000"))

Also, on average, you can notice, that unfurnished houses, are less expensive.

We can also take a look on some pie charts:


dt_mainroad_counts <- as.data.frame(table(dt_houses$mainroad)) #table() - creates frequency table
colnames(dt_mainroad_counts) <- c("mainroad_status", "count")
dt_mainroad_counts$percentage <- round(dt_mainroad_counts$count / sum(dt_mainroad_counts$count) * 100, 1)

ggplot(data = dt_mainroad_counts, aes(x = "", y = count, fill = mainroad_status)) +
  geom_bar(stat = "identity", width = 1, color = "white") +
  coord_polar("y", start = 0) +
  geom_text(aes(label = paste0(percentage, "%")), 
            position = position_stack(vjust = 0.5), color = "white", size = 4) +  
  theme_void() +  
  scale_fill_manual(values = c("#F1B147", "#47B1F1")) + 
  labs(
    title = "Distribution of Mainroad Status",
    fill = "Mainroad Status"
  )

Almost 86 percent of houses have main road, so maybe this won’t be a strong predictor variable.


dt_airconditioning_counts <- as.data.frame(table(dt_houses$airconditioning)) #table() - creates frequency table
colnames(dt_airconditioning_counts) <- c("airconditioning_status", "count")
dt_airconditioning_counts$percentage <- round(dt_airconditioning_counts$count / sum(dt_airconditioning_counts$count) * 100, 1)

ggplot(data = dt_airconditioning_counts, aes(x = "", y = count, fill = airconditioning_status)) +
  geom_bar(stat = "identity", width = 1, color = "white") +
  coord_polar("y", start = 0) +
  geom_text(aes(label = paste0(percentage, "%")), 
            position = position_stack(vjust = 0.5), color = "white", size = 4) +  
  theme_void() +  
  scale_fill_manual(values = c("#F1B147", "#47B1F1")) + 
  labs(
    title = "Distribution of Airconditioning status",
    fill = "Airconditioning Status"
  )

Here 68.4 percent has airconditioning, but I do not know, how it will affect predictions.

I think that would be enough exploration and we can start with models.

Models 1 & 2

  • Evaluate and compare your models based on a reasonable evaluation metric of your choice. You must use the same metric for both models. Report both the training and the CV loss.

First, I would like to start pretty simple with linear model.

I consider to take all variables to my model, because they all seem to be very important.

Linear model

I will use lm function in R to find needed beta coefficients and create my model

price_lm <- lm(formula = price ~ area + bedrooms + hotwaterheating + airconditioning + stories + mainroad + parking + furnishingstatus + bathrooms + guestroom + basement + prefarea, data = dt_houses)

summary(price_lm)

Call:
lm(formula = price ~ area + bedrooms + hotwaterheating + airconditioning + 
    stories + mainroad + parking + furnishingstatus + bathrooms + 
    guestroom + basement + prefarea, data = dt_houses)

Residuals:
     Min       1Q   Median       3Q      Max 
-2619718  -657322   -68409   507176  5166695 

Coefficients:
                                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)                      42771.69  264313.31   0.162 0.871508    
area                               244.14      24.29  10.052  < 2e-16 ***
bedrooms                        114787.56   72598.66   1.581 0.114445    
hotwaterheatingyes              855447.15  223152.69   3.833 0.000141 ***
airconditioningyes              864958.31  108354.51   7.983 8.91e-15 ***
stories                         450848.00   64168.93   7.026 6.55e-12 ***
mainroadyes                     421272.59  142224.13   2.962 0.003193 ** 
parking                         277107.10   58525.89   4.735 2.82e-06 ***
furnishingstatussemi-furnished  -46344.62  116574.09  -0.398 0.691118    
furnishingstatusunfurnished    -411234.39  126210.56  -3.258 0.001192 ** 
bathrooms                       987668.11  103361.98   9.555  < 2e-16 ***
guestroomyes                    300525.86  131710.22   2.282 0.022901 *  
basementyes                     350106.90  110284.06   3.175 0.001587 ** 
prefareayes                     651543.80  115682.34   5.632 2.89e-08 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1068000 on 531 degrees of freedom
Multiple R-squared:  0.6818,    Adjusted R-squared:  0.674 
F-statistic: 87.52 on 13 and 531 DF,  p-value: < 2.2e-16

We got 0.68 R-squared, which is not that bad for a model just made up. But that’s not all, I will try to do better here, but first, another model.

But I would like to measure performance of my models with RMSE, so I will calculate RMSE for linear model.

price_lm_rmse <- mean(sqrt(abs(price_lm$residuals)))

price_lm_rmse
[1] 797.382

Tree Model

I think this model could perform better, because there some variables which can affect this model not only linearly, but the other way, in this case tree model can show better performance.

In this coursework will be used rpart to create a regression tree.

prices_tree <- rpart(data = dt_houses, formula = price ~ area + bedrooms + hotwaterheating + airconditioning + stories + mainroad + parking + furnishingstatus + bathrooms + guestroom + basement + prefarea, method = 'anova')

prp(prices_tree, digits = -3)

printcp(prices_tree)

Regression tree:
rpart(formula = price ~ area + bedrooms + hotwaterheating + airconditioning + 
    stories + mainroad + parking + furnishingstatus + bathrooms + 
    guestroom + basement + prefarea, data = dt_houses, method = "anova")

Variables actually used in tree construction:
[1] airconditioning  area             basement         bathrooms        furnishingstatus parking         

Root node error: 1.9032e+15/545 = 3.4921e+12

n= 545 

         CP nsplit rel error  xerror     xstd
1  0.304946      0   1.00000 1.00308 0.085214
2  0.094553      1   0.69505 0.73722 0.064021
3  0.053743      2   0.60050 0.63705 0.055702
4  0.026381      3   0.54676 0.60469 0.052423
5  0.024922      4   0.52038 0.63046 0.056608
6  0.022993      5   0.49546 0.63335 0.056276
7  0.021374      6   0.47246 0.63383 0.056693
8  0.015261      7   0.45109 0.60699 0.055365
9  0.013952      8   0.43583 0.59339 0.055453
10 0.012386      9   0.42188 0.57479 0.054000
11 0.010000     10   0.40949 0.54864 0.050812

Now after I have built with the help of rpart tree model based on my dataset, let us explore it:

prices_tree
n= 545 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 545 1.903208e+15 4766729  
   2) area< 5954 361 6.066751e+14 4029993  
     4) bathrooms< 1.5 293 3.297298e+14 3773561  
       8) area< 4016 174 1.437122e+14 3431227  
        16) furnishingstatus=unfurnished 78 4.036605e+13 2977962 *
        17) furnishingstatus=furnished,semi-furnished 96 7.430067e+13 3799505 *
       9) area>=4016 119 1.358098e+14 4274118 *
     5) bathrooms>=1.5 68 1.746610e+14 5134912  
      10) airconditioning=no 44 7.024826e+13 4563682 *
      11) airconditioning=yes 24 6.373358e+13 6182167 *
   3) area>=5954 184 7.161564e+14 6212174  
     6) bathrooms< 1.5 108 2.869179e+14 5382579  
      12) airconditioning=no 65 1.170629e+14 4843569  
        24) basement=no 38 5.226335e+13 4304816 *
        25) basement=yes 27 3.824662e+13 5601815 *
      13) airconditioning=yes 43 1.224240e+14 6197360 *
     7) bathrooms>=1.5 76 2.492851e+14 7391072  
      14) parking< 1.5 51 7.184700e+13 6859794 *
      15) parking>=1.5 25 1.336772e+14 8474878  
        30) airconditioning=no 10 5.146311e+13 7285600 *
        31) airconditioning=yes 15 5.864106e+13 9267729 *

We can see, that we have 31 Nodes, I think for this kind of dataset it may be okay.

Now it would be great to prune the tree, because I do not want my tree to overfit:

plotcp(prices_tree)

This is complexity of this tree. We need the lowest complexity, to get as few leafs as possible to get the best performance, so that tree won’t overfit the data.

prices_tree_min_cp <- prices_tree$cptable[which.min(prices_tree$cptable[, "xerror"]), "CP"]
model_tree <- prune(prices_tree, cp = prices_tree_min_cp )
prp(prices_tree,digits = -3)

after we pruned the tree, let’s calculate the RMSE for the tree model

prices_tree_pred <- predict(prices_tree, dt_houses[, c("area","bathrooms", "bedrooms", "hotwaterheating", "airconditioning", "parking", "stories", "mainroad", "furnishingstatus", "guestroom", "basement", "prefarea")])
prices_tree_rmse <- mean(sqrt(abs(dt_houses$price - prices_tree_pred)))

prices_tree_rmse
[1] 860.0223

Ensemble

  • Repeat the analysis one ensemble method of your choice.
  • Investigate the hyperparameter settings of your ensemble with regards to your evaluation metric.
  • Report both the training and the CV loss.
  • Select the best configurations of your ensemble model based on the same evaluation metric as before.

Neural Network

  • Repeat the analysis with a neural network.
  • Investigate three different configurations with regards to your evaluation metric and select the best configuration. Use the same evaluation metric as before.
  • Report both the training and the CV loss.

Model Comparison

  • Compare the performance of the 4 models.

PCA

  • Run a PCA on your input variables and discuss the scope for dimensionality reduction in your dataset.
  • Rerun the previous 4 models on all PCs or on a reduced number of PCs.

Calculating PCs

Before I start working with PCA, this is important to normalize the data, so that measurement scale will not affect PCs.

dt_pca <- data.table(scale(dt_houses[,c("area", "bedrooms", "bathrooms", "stories", "parking")]))

Now I want to run my PCA with help of prcomp and get the summary, to dive in to Data.

summary(dt_houses_pca)
Importance of components:
                          PC1    PC2    PC3    PC4    PC5
Standard deviation     1.3878 1.0927 0.8112 0.8031 0.7597
Proportion of Variance 0.3852 0.2388 0.1316 0.1290 0.1154
Cumulative Proportion  0.3852 0.6240 0.7556 0.8846 1.0000

That looks interesting, but I would like to plot it, to visualize it, so that it could be easier to understand.

fviz_eig(dt_houses_pca, addlabels = TRUE) + 
         theme(plot.title = element_text(hjust = 0.5))

From this plot it is obvious that all PC are useful and reducing dimensionality will not be beneficial, because last 3 PCs contribute approx. 12% each, which is a fair amount in this case. So I would like to use all PCs, because it is not always necessary to cut dimensionality and it depends on how much variablse we actually have, and how big of a contribution makes each PC. May be in this case “change of basis” will perform better.

Running models with PCs

Now as I have calculated PCs, it is time to run models with new inputs.

Linear model

First we will start off with linear model.

pc_table <- dt_houses_pca$x

dt_houses_pc_table_for_lm <- data.frame(price = dt_houses$price, pc_table)

price_lm_pca <- lm(formula = price ~ ., data = dt_houses_pc_table_for_lm)

summary(price_lm_pca)

Call:
lm(formula = price ~ ., data = dt_houses_pc_table_for_lm)

Residuals:
     Min       1Q   Median       3Q      Max 
-3396744  -731825   -64056   601486  5651126 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  4766729      53296  89.439  < 2e-16 ***
PC1          -950194      38439 -24.719  < 2e-16 ***
PC2          -295175      48820  -6.046 2.77e-09 ***
PC3            86943      65760   1.322    0.187    
PC4           320544      66423   4.826 1.82e-06 ***
PC5          -296109      70223  -4.217 2.91e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1244000 on 539 degrees of freedom
Multiple R-squared:  0.5616,    Adjusted R-squared:  0.5575 
F-statistic: 138.1 on 5 and 539 DF,  p-value: < 2.2e-16
price_lm_pca_rmse <- mean(sqrt(abs(price_lm_pca$residuals)))

price_lm_pca_rmse
[1] 857.5466

Now, after PCA the results are worse and I think this is because there are fair amount of binary variables in this dataset and PCA could not capture all information, espesially information, which is captured in binary variables, such as “mainroad”, “airconditioning” and so on.

Model Selection

  • Compare all of your models based on their CV performance. You will have 8 options to consider: four models with and without PCA.
  • Present the results in a table or chart.
  • Estimate the expected loss of your best model on the test set.

LS0tCnRpdGxlOiAiQ291cnNld29yayAtIERhdGEgU2NpZW5jZSBJSSIKYXV0aG9yOiAiT21hciBaaGFkeWtvdiwgMjIwMjIwNTAzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGZpZ193aWR0aDogMTAKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICB3b3JkX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogJzMnCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDoKICAgIGZpZ193aWR0aDogMTAKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgo8c2NyaXB0PgokKGRvY3VtZW50KS5yZWFkeShmdW5jdGlvbigpIHsKICAkaXRlbXMgPSAkKCdkaXYjVE9DIGxpJyk7CiAgJGl0ZW1zLmVhY2goZnVuY3Rpb24oaWR4KSB7CiAgICBudW1fdWwgPSAkKHRoaXMpLnBhcmVudHNVbnRpbCgnI1RPQycpLmxlbmd0aDsKICAgICQodGhpcykuY3NzKHsndGV4dC1pbmRlbnQnOiBudW1fdWwgKiAxMCwgJ3BhZGRpbmctbGVmdCc6IDB9KTsKICB9KTsKCn0pOwo8L3NjcmlwdD4KCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBlY2hvPUZBTFNFfQpsaWJyYXJ5KHN2Z2xpdGUpCmxpYnJhcnkoa25pdHIpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGRhdGEudGFibGUpKQpsaWJyYXJ5KGdncGxvdDIpCmtuaXRyOjpvcHRzX2NodW5rJHNldChkZXYgPSAic3ZnbGl0ZSIpCgojIFB1dCB5b3VyIGRhdGFzZXQgaW4gdGhlIHNhbWUgZm9sZGVyIGFzIHlvdXIgUiBmaWxlLiBUaGlzIGNvZGUgd2lsbCBzZXQgeW91ciB3b3JraW5nIGRpcmVjdG9yeSBmb3IgdGhpcyBub3RlYm9vayB0byB0aGUgZm9sZGVyIHdoZXJlIHRoZSBSIGZpbGUgaXMgc3RvcmVkLiBUaGlzIHdheSBJIGNhbiByZXJ1biB5b3VyIGNvZGUgd2l0aG91dCBtb2RpZmljYXRpb25zLgoKbGlicmFyeShyc3R1ZGlvYXBpKQpzZXR3ZChkaXJuYW1lKGdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGgpKQpgYGAKCiMgSW50cm9kdWN0aW9uCgpJbiB0aGlzIGNvdXJzZSB3b3JrIEkgd2lsbCBleHBsb3JlIGRhdGFzZXQgYWJvdXQgc3R1ZGVudHMnIHBlcmZvcm1hbmNlIGJhc2VkIG9uIHRoZWlyIGhhYml0cyBhbmQgbGlmZXN0eWxlLiBJIGhhdmUgY2hvc2VuIHRoaXMgZGF0YXNldCwgYmVjYXVzZSBpdCBpcyByaWNoIG9uIGRpZmZlcmVudCBkaXNjcmV0ZSwgY29udGluaW91cyB2YXJpYWJsZXMgd2l0aCBhIGdvb2QgcmFuZ2UgYW5kIEkgYXNzdW1lIGdvb2QgdmFyaWFuY2UsIHdoaWNoIHdpbGwgYmUgdmVyeSB1c2VmdWwgaW4gRGF0YSBTY2llbmNlIDIgY2xhc3MuIAoKVGhpcyBkYXRhc2V0IGNvbnNpc3RzIG9mIDE2IGNvbHVtbnMgd2hpY2ggY291bGQgYmUgY2F0ZWdvcml6ZWQgaW50byB0aGVzZSBncm91cHM6CiAgLSBkaXNjcmV0ZSB2YXJpYWJsZXMgKGV4YW1wbGU6IGV4ZXJjaXNlIGZyZXF1ZW5jeSBwZXIgd2VlaykKICAtIGNvbnRpbmlvdXMgdmFyaWFibGVzIChleGFtcGxlOiBzbGVlcCBxdWFudGl0eSkKICAtIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAoZXhhbXBsZSBwYXIgdGltZSBqb2IsIHllcyBvciBubykKICAKVGhpcyBpcyByZWdyZXNzaW9uIHByb2JsZW0sIGJlY2F1c2UgSSB3aWxsIHRyeSB0byBwcmVkaWN0IHRoZSBzdHVkZW50J3Mgc2NvcmUgZm9yIGFuIGV4YW0sIGJhc2VkIG9uIGRpZmZlcmVudCB2YXJpYWJsZXMuCgojIERlc2NyaXB0aW9uCgotIENvbGxlY3QgeW91ciBkYXRhc2V0KHMpLCBleHBsb3JlIHlvdXIgZGF0YSBmb3IgZGVmaWNpZW5jaWVzIHN1Y2ggYXMgbWlzc2luZyBkYXRhIGFuZCBmb3JtYXR0aW5nIHByb2JsZW1zIGFuZCBwcmVwYXJlIGl0IGZvciBtb2RlbGxpbmcuIAotIEV4dGVuc2l2ZSBkYXRhIGNvbGxlY3Rpb24gYW5kIHByZXBhcmF0aW9uIHlpZWxkcyBleHRyYSBjcmVkaXQgYnV0IGlzIG5vdCBtYW5kYXRvcnkgZm9yIHRoaXMgY291cnNld29yay4gCi0gRXhwbG9yZSB0aGUgZGF0YSB2aWEgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBhbmQgdmlzdWFsaXphdGlvbi4KCiMjIyBDb2xsZWN0aW9uCgpIZXJlIEkgd291bGQgbGlrZSB0byBjb2xsZWN0LCBwcmVwYXJlLCBhbmQgZXhwbG9yZSBteSBkYXRhLiBGaXJzdCB0aGluZyBpcyB0byBpbXBvcnQgdGhlIGRhdGEgc2V0LgoKYGBge3J9CmR0X3N0dWRlbnRzIDwtIGZyZWFkKGZpbGUgPSAiLi9kYXRhL3N0dWRlbnRfaGFiaXRzX3BlcmZvcm1hbmNlLmNzdiIpCmBgYAoKPGJyPgoKSSB3b3VsZCBsaWtlIHRvIGNoZWNrLCBpZiBpIGhhdmUgc29tZSBudWxsaXNoIGRhdGEgaW4gbXkgZGF0YXNldC4gSSB0aGluayBpdCBpcyBhIGdvb2QgaWRlYSB0byBnbyB0aHJvdWdoIGFsbCByb3dzIGFuZCBjb2x1bXMgYW5kIGNoZWNrLCBpZiB0aGVyZSBpcyBhIE5BLiBJIHdhbnQgdG8gY2hlY2sgaXQgd2l0aCBidWlsdC1pbiBmdW5jdGlvbiBpbiBSICpjb21wbGV0ZS5jYXNlcyhkYXRhX3RhYmxlKSouIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBUUlVFIG9yIEZBTFNFIGlmIHJvdyBjb250YWlucyBhIE5BIHZhbHVlLgoKYGBge3J9Cm5hcyA8LSBkdF9zdHVkZW50c1shY29tcGxldGUuY2FzZXMoZHRfc3R1ZGVudHMpXQpuYXMKYGBgCgpUaGF0IGxvb2tzIGdyZWF0LiBOb3cgd2UgY2FuIG1vdmUgb24gdG8gZXhwbG9yYXRpb24uIEJ1dCBiZWZvcmUgSSBzdGFydCwgSXQgaXMgY3J1Y2lhbCB0byBpbnN0YWxsIGFsbCBuZWVkZWQgbGlicmFyaWVzLgoKYGBge3J9CmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShnZ2NvcnJwbG90KQpsaWJyYXJ5KGdnRXh0cmEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3JpZGdlcykKbGlicmFyeShnZ3NjaSkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoc3ZnbGl0ZSkKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHNjYWxlcykKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmBgYAoKIyMjIEV4cGxvcmF0aW9uCgpJIGZvdW5kIHNvbWUgaGVscGZ1bCBmdW5jdGlvbnMgaW4gUiwgc28gd2UgY291bGQgaGF2ZSBhIGxvb2sgb24gb3VyIGRhdGEuIFdlIHdpbGwgc3RhcnQgd2l0aCBhIHN0cnVjdHVyZSwgdGhhbiB3ZSB3aWxsIGdldCBzb21lIHN0YXRpc3RpYyBkYXRhIGFuZCB0YWtlIGEgKmhlYWQoKSogb2YgdGhlIGRhdGEKCmBgYHtyfQpzdHIoZHRfc3R1ZGVudHMpCmBgYAo8YnI+ClN0YXRpc3RpYyBkYXRhOgpgYGB7cn0Kc3VtbWFyeShkdF9zdHVkZW50cykKYGBgCgo8YnI+CmFuZCB0aGlzIGlzIGEgc2FtcGxlIG9mIGRhdGFzZXQ6CgpgYGB7cn0KaGVhZChkdF9zdHVkZW50cykKYGBgCgpJIHdvdWxkIGxpa2UgdG8gc3RhcnQgZnJvbSBkZW5zaXR5IG9mIGEgbWFpbiB2YWx1ZXMsIHdoaWNoIGFyZSBmcm9tIG15IGRvbWFpbiBrbm93bGVkZ2UgYXJlIGltcG9ydGFudCBmb3IgdGhlIGJlc3QgcGVyZm9ybWFuY2UgYXQgdGhlIHVuaXZlcnNpdHkuCgogZGVuc2l0eTogCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkdF9zdHVkZW50cywgYWVzKHggPSBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UpKSArIAogIGdlb21fZGVuc2l0eShmaWxsPSIjZjFiMTQ3IiwgY29sb3I9IiNmMWIxNDciLCBhbHBoYT0wLjI1KSArIAogIGxhYnMoCiAgICB4ID0gJ1ByaWNlJywKICAgIHkgPSAnRGVuc2l0eScKICApICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBtZWFuKGR0X3N0dWRlbnRzJGF0dGVuZGFuY2VfcGVyY2VudGFnZSksIGxpbmV0eXBlPSJkYXNoZWQiKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShjb2xvciA9ICIjMDAwMDAwIikpCmBgYApUaGlzIGRlbnNpdHkgcGxvdCB2aXN1YWxpemVzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGF0dGVuZGFuY2UgcGVyY2VudGFnZSwgc2hvd2luZyB0aGF0IG1vc3Qgc3R1ZGVudHMgYXR0ZW5kIGNsYXNzZXMgYXQgYSByYXRlIG9mIH44NSUgcm91Z2hseSwgYW5kIHRoaXMgaXMgYSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLiBUaGUgZGFzaGVkIHZlcnRpY2FsIGxpbmUgcmVwcmVzZW50cyB0aGUgbWVhbiBhdHRlbmRhbmNlIHBlcmNlbnRhZ2UgKH44NC04NSUpLiBUaGUgcGxvdCBzaG93cywgdGhhdCB0aGUgbWFqb3JpdHkgb2Ygc3R1ZGVudHMgYXJlIGF0dGVuZGluZyBtb3N0IG9mIHRoZSBjbGFzc2VzLgoKCkFyZWEgZGVuc2l0eToKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGR0X2hvdXNlcywgYWVzKHggPSBhcmVhKSkgKyAKICBnZW9tX2RlbnNpdHkoZmlsbD0iI2YxYjE0NyIsIGNvbG9yPSIjZjFiMTQ3IiwgYWxwaGE9MC4yNSkgKyAKICBsYWJzKAogICAgeCA9ICdQcmljZScsCiAgICB5ID0gJ0RlbnNpdHknCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG9yID0gIiMwMDAwMDAiKSkKYGBgClRoZSBhcmVhIGRlbnNpdHkgcGxvdCBsb29rcyBzaW1pbGFyIHRvIHByaWNlIGRlbnNpdHkgcGxvdCBhbmQgY2FuIGFsc28gbWFrZSBzZW5zZSwgYmVjYXVzZSBpZiBob3VzZSBoYXMgYSBiaWdnZXIgYXJlYSwgdGhlIGhpZ2hlciBjb3N0IGlzIHF1aXRlIGV4cGVjdGVkLiBUaGlzIHBsb3Qgc2hvd3MgdGhhdCBtb3N0IGhvdXNlcyBhcmUgaGF2aW5nIGFyZWEgaW4gcmFuZ2UgfjMwMDAtNTAwMC4gQnV0IHNvbWUgcHJvcGVydGllcyBoYXZlIGFyZWEgbW9yZSB0aGFuIDEyMDAwLgoKPGJyPgoKTmV4dCBwbG90IHdpbGwgdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgcHJpY2UgZGVwZW5kaW5nIG9uIGFyZWEuIAoKYGBge3J9CmdncGxvdCgpICsgCiAgZ2VvbV9wb2ludChkYXRhID0gZHRfaG91c2VzLCBhZXMoeCA9IGFyZWEsIHkgPSBwcmljZSwgY29sb3IgPSBwYXJraW5nKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9udW1iZXIoc2NhbGUgPSAxZS02LCBzdWZmaXggPSAiTSIpKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShjb2xvciA9ICIjMDAwMDAwIikpCmBgYAoKVGhpcyBzY2F0dGVyIHBsb3QgdmlzdWFsaXplcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gaG91c2UgYXJlYSAoeC1heGlzKSBhbmQgcHJpY2UgKHktYXhpcyksIHdpdGggY29sb3IgaW5kaWNhdGluZyB0aGUgbnVtYmVyIG9mIHBhcmtpbmcgc3BhY2VzLiBJdCBzaG93cyBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gYXJlYSBhbmQgcHJpY2XigJRsYXJnZXIgaG91c2VzIHRlbmQgdG8gYmUgbW9yZSBleHBlbnNpdmUuIEhvd2V2ZXIsIHRoZXJlIGlzIHNvbWUgdmFyaWFiaWxpdHksIGFzIHNvbWUgbGFyZ2UgaG91c2VzIGhhdmUgcmVsYXRpdmVseSBsb3dlciBwcmljZXMuIFRoZSBjb2xvciBncmFkaWVudCBzdWdnZXN0cyB0aGF0IGhvdXNlcyB3aXRoIG1vcmUgcGFya2luZyBzcGFjZXMgKGxpZ2h0ZXIgYmx1ZSkgdGVuZCB0byBiZSBoaWdoZXIgaW4gcHJpY2UgYW5kIGxhcmdlciBpbiBhcmVhLgoKVGhlIG5leHQgcGxvdCwgd2hpY2ggSSBhbSBnb2luZyB0byBkbyBpcyBhIGJveHBsb3QgYW5kIEkgd2FudCB0byB1c2UgYmVkcm9vbXMgYXMgYSBmYWN0b3IgdmFyaWFibGUgb24geCBheGlzIGFuZCBwcmljZSBvbiB5LWF4aXMsIHRvIGdldCBhbiBvdmVyYWxsIHVuZGVyc3RhbmRpbmcsIGhvdyBhbW91bnQgb2YgYmVkcm9vbXMgYWZmZWN0IHByaWNlLgoKYGBge3J9CmdncGxvdChkYXRhID0gZHRfaG91c2VzLCBhZXMoeCA9IGZhY3RvcihiZWRyb29tcyksIHkgPSBwcmljZSkpICsKICBnZW9tX2JveHBsb3QoKSArIAogIHRoZW1lX21pbmltYWwoKSAKYGBgCgpCb3hwbG90IHNob3dzLCB0aGF0IG9uIGF2ZXJhZ2UsIGhvdXNlcyB3aXRoIG1vcmUgYmVkcm9vbXMgaGF2ZSBoaWdoZXIgcHJpY2VzLCBidXQgYXJvdW5kIDQtNiBiZWRyb29tcywgMSBxdWFudGlsZSBzdGFnbmF0ZXMsIGFuZCBzbyBkb2VzIG1lZGlhbiBwcmljZS4gVGhlcmUgYXJlIHNvbWUgb3V0bGllcnMsIGJ1dCBub3QgdG9vIG11Y2guCgpJdCBpcyBhbHNvIGludGVyZXN0aW5nIHRvIHRha2UgYSBsb29rIGF0IGRpc3RyaWJ1dGlvbiBvZiBiZWRyb29tcywgc28gbmV4dCBwbG90IHdvdWxkIGJlIGEgaGlzdG9ncmFtLCBiZWNhdXNlIEkgd2FudCB0byBrbm93LCB3aGljaCBhbW91bnQgb2YgYmVkcm9vbXMgaXMgdGhlIG1vc3QgInBvcHVsYXIiIGluIHRoZSB3aG9sZSBkYXRhc2V0LgoKYGBge3J9CmdncGxvdChkYXRhID0gZHRfaG91c2VzLCBhZXMoeCA9IGJlZHJvb21zKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShmaWxsPSIjMmY5ZTQ0IiwgY29sb3I9IiMyZjllNDQiLCBhbHBoYT0wLjI1KSArIAogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IG1lYW4oZHRfaG91c2VzJGJlZHJvb21zKSwgbGluZXR5cGU9ImRhc2hlZCIpICsgCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG9yID0gIiMwMDAwMDAiKSkKYGBgCm1lYW4gb2YgdGhlIGJlZHJvb21zOgpgYGB7cn0KbWVhbihkdF9ob3VzZXMkYmVkcm9vbXMpCmBgYAoKRnJvbSB0aGlzIHZpc3VhbGl6YXRpb24gd2UgY2FuIG1lbnRpb24sIHRoYXQgdGhlIG1vc3Qgb2YgdGhlIGhvdXNlcyBoYXZlIDIsIDMgb3IgNCByb29tcy4gMSwgNSBhbmQgNiByb29tcyBhcmUgbm90IGFzIHBvcHVsYXIgaW4gdGhpcyBkYXRhc2V0LgoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgaGlzdG9ncmFtIG9mIHN0b3JpZXM6IAoKYGBge3J9CmdncGxvdChkYXRhID0gZHRfaG91c2VzLCBhZXMoeCA9IHN0b3JpZXMpKSArIAogIGdlb21faGlzdG9ncmFtKGZpbGw9IiMyZjllNDQiLCBjb2xvcj0iIzJmOWU0NCIsIGFscGhhPTAuMjUpICsgCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbihkdF9ob3VzZXMkc3RvcmllcyksIGxpbmV0eXBlPSJkYXNoZWQiKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShjb2xvciA9ICIjMDAwMDAwIikpCmBgYAoKYGBge3J9Cm1lYW4oZHRfaG91c2VzJHN0b3JpZXMpCmBgYAoKVGhpcyBwbG90IHNob3dzIHRoYXQgbW9zdCBwb3B1bGFyIGFtb3VudCBvZiBzdG9yaWVzIGFyZSAxIGFuZCAyLiAzIGFuZCA0IG1ha2VpbmcgbGVzcyB0aGFuIDEwMCBob3VzZXMgdG9nZXRoZXIuCgpCYXRocm9vbXMgYXJlIGFsc28gaW50ZXJlc3RpbmcgdmFyaWFibGUsIHNvIGxldCdzIHRha2UgYSBsb29rIGF0IGhpc3RvZ3JhbSBhbmQgYSBCb3hwbG90IGJhdGhyb29tcyBhbmQgcHJpY2U6CmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGR0X2hvdXNlcywgYWVzKHggPSBiYXRocm9vbXMpKSArIAogIGdlb21faGlzdG9ncmFtKGZpbGw9IiMyZjllNDQiLCBjb2xvcj0iIzJmOWU0NCIsIGFscGhhPTAuMjUpICsgCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbihkdF9ob3VzZXMkYmF0aHJvb21zKSwgbGluZXR5cGU9ImRhc2hlZCIpICsgCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG9yID0gIiMwMDAwMDAiKSkKYGBgCgoKYGBge3J9CmdncGxvdChkYXRhID0gZHRfaG91c2VzLCBhZXMoeCA9IGZhY3RvcihiYXRocm9vbXMpLCB5ID0gcHJpY2UpKSArCiAgZ2VvbV9ib3hwbG90KCkgKyAKICB0aGVtZV9taW5pbWFsKCkgCmBgYAoKaGVyZSBpdCBpcyBhbHNvIGFsbW9zdCBvYnZpb3VzLCB0aGF0LCBpZiB3ZSBoYXZlIG1vcmUgYmF0aHJvb21zLCBwcmljZSB3aWxsIGJlIGFsc28gdXAuIE9ubHkgb25lIGRpc2FkdmFudGFnZSwgdGhhdCBpbiBteSBkYXRhc2V0IEkgZG8gbm90IGhhdmUgZW5vdWdoIGRhdGEgYWJvdXQgcHJvcGVydGllcyB3aXRoIDMgb3IgNCBiYXRocm9vbXMsIEkgaGF2ZSBzb21lIG9uIDMsIGJ1dCByZWFsbHkgbHVjayBvbiA0LgoKRnVybmlzaGluZyBpcyBhbHNvIGltcG9ydGFudCwgbWFueSBwZW9wbGUgc2VhcmNoIGZvciBhcGFydG1lbnRzIHdpdGggZnVybml0dXJlLCBidXQgZnVybml0dXJlIGNvdWxkIGJlIG5vdCBpbiBhIGJlc3Qgc2hhcGUgb3IgYnV5ZXIgbWF5IGRvIG5vdCBsaWtlIHRoZSBzdHlsZS4gU28gZnJvbSBteSBvcGluaW9uLCBpdCBpcyBub3QgYXMgc3Ryb25nKGluIHByZWRpY3Rpb24pLCBhcyBmb3IgZXhhbXBsZSBhcmVhLgoKSG93IG11Y2ggcmVhbCBlc3RhdGUgZnVybmlzaGVkIG9yIG5vdDoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGR0X2hvdXNlcywgYWVzKHggPSBmYWN0b3IoZnVybmlzaGluZ3N0YXR1cyksIGZpbGwgPSBmYWN0b3IoZnVybmlzaGluZ3N0YXR1cykpKSArIAogIGdlb21fYmFyKGNvbG9yPSIjY2VkNGRhIiwgYWxwaGE9MC4yNSkgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiRCIpICsgCiAgbGFicyh0aXRsZSA9ICJCYXIgQ2hhcnQgd2l0aCBEaWZmZXJlbnQgQ29sb3JzIiwgCiAgICAgICB4ID0gIkZ1cm5pc2hpbmcgU3RhdHVzIiwgCiAgICAgICB5ID0gIkNvdW50IikgKyAKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAiIzAwMDAwMCIpKQpgYGAKCldlIGNhbiBzZWUsIHRoYXQgbW9zdCBvZiB0aGUgaG91c2VzIGFyZSBzZW1pLWZ1cm5pc2hlZC4gd2hpY2ggaXMgYWxzbyBsb2dpY2FsLCBiZWNhdXNlIHdoZW4gd2Ugc2VsbCBhIGhvdXNlIG9yIGFwYXJ0bWVudCwgcHJvYmFibHkgd2Ugd291bGQgdGFrZSBpbiBtb3N0IG9mIHRoZSBjYXNlcyB0aGUgbW9zdCB2YWx1YWJsZSB0aGluZ3MgZm9yIHVzIGFuZCBmdXJuaXR1cmUgaW5jbHVkZWQuCgpOb3csIGl0IHdvdWxkIGJlIGdyZWF0LCB0byBsb29rIGF0IHByaWNlIGFuZCBhcmVhIGRpc3RyaWJ1dGlvbiBpbiBkaWZmZXJlbnRseSBmdXJuaXNoZWQgcHJvcGVydGllcwoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGR0X2hvdXNlcywgYWVzKHkgPSBwcmljZSwgeCA9IGFyZWEpKSArIAogIGdlb21fcG9pbnQoZGF0YSA9IGR0X2hvdXNlcywgYWVzKHkgPSBwcmljZSwgeCA9IGFyZWEsIGNvbG9yID0gYmVkcm9vbXMpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihkdF9ob3VzZXMkcHJpY2UpLCBsaW5ldHlwZT0nZGFzaGVkJykgKyAKICBmYWNldF9ncmlkKC5+ZnVybmlzaGluZ3N0YXR1cykgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9udW1iZXIoc2NhbGUgPSAxZS02LCBzdWZmaXggPSAiTSIpKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKHR5cGUgPSAic2VxIiwgcGFsZXR0ZSA9ICJHcmVlbnMiKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG9yID0gIiMwMDAwMDAiKSkKYGBgCgpBbHNvLCBvbiBhdmVyYWdlLCB5b3UgY2FuIG5vdGljZSwgdGhhdCB1bmZ1cm5pc2hlZCBob3VzZXMsIGFyZSBsZXNzIGV4cGVuc2l2ZS4KCldlIGNhbiBhbHNvIHRha2UgYSBsb29rIG9uIHNvbWUgcGllIGNoYXJ0czoKCmBgYHtyfQoKZHRfbWFpbnJvYWRfY291bnRzIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoZHRfaG91c2VzJG1haW5yb2FkKSkgI3RhYmxlKCkgLSBjcmVhdGVzIGZyZXF1ZW5jeSB0YWJsZQpjb2xuYW1lcyhkdF9tYWlucm9hZF9jb3VudHMpIDwtIGMoIm1haW5yb2FkX3N0YXR1cyIsICJjb3VudCIpCmR0X21haW5yb2FkX2NvdW50cyRwZXJjZW50YWdlIDwtIHJvdW5kKGR0X21haW5yb2FkX2NvdW50cyRjb3VudCAvIHN1bShkdF9tYWlucm9hZF9jb3VudHMkY291bnQpICogMTAwLCAxKQoKZ2dwbG90KGRhdGEgPSBkdF9tYWlucm9hZF9jb3VudHMsIGFlcyh4ID0gIiIsIHkgPSBjb3VudCwgZmlsbCA9IG1haW5yb2FkX3N0YXR1cykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZTAocGVyY2VudGFnZSwgIiUiKSksIAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gNCkgKyAgCiAgdGhlbWVfdm9pZCgpICsgIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNGMUIxNDciLCAiIzQ3QjFGMSIpKSArIAogIGxhYnMoCiAgICB0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgTWFpbnJvYWQgU3RhdHVzIiwKICAgIGZpbGwgPSAiTWFpbnJvYWQgU3RhdHVzIgogICkKCmBgYAoKQWxtb3N0IDg2IHBlcmNlbnQgb2YgaG91c2VzIGhhdmUgbWFpbiByb2FkLCBzbyBtYXliZSB0aGlzIHdvbid0IGJlIGEgc3Ryb25nIHByZWRpY3RvciB2YXJpYWJsZS4KCgpgYGB7cn0KCmR0X2FpcmNvbmRpdGlvbmluZ19jb3VudHMgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShkdF9ob3VzZXMkYWlyY29uZGl0aW9uaW5nKSkgI3RhYmxlKCkgLSBjcmVhdGVzIGZyZXF1ZW5jeSB0YWJsZQpjb2xuYW1lcyhkdF9haXJjb25kaXRpb25pbmdfY291bnRzKSA8LSBjKCJhaXJjb25kaXRpb25pbmdfc3RhdHVzIiwgImNvdW50IikKZHRfYWlyY29uZGl0aW9uaW5nX2NvdW50cyRwZXJjZW50YWdlIDwtIHJvdW5kKGR0X2FpcmNvbmRpdGlvbmluZ19jb3VudHMkY291bnQgLyBzdW0oZHRfYWlyY29uZGl0aW9uaW5nX2NvdW50cyRjb3VudCkgKiAxMDAsIDEpCgpnZ3Bsb3QoZGF0YSA9IGR0X2FpcmNvbmRpdGlvbmluZ19jb3VudHMsIGFlcyh4ID0gIiIsIHkgPSBjb3VudCwgZmlsbCA9IGFpcmNvbmRpdGlvbmluZ19zdGF0dXMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMSwgY29sb3IgPSAid2hpdGUiKSArCiAgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUwKHBlcmNlbnRhZ2UsICIlIikpLCAKICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDQpICsgIAogIHRoZW1lX3ZvaWQoKSArICAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjRjFCMTQ3IiwgIiM0N0IxRjEiKSkgKyAKICBsYWJzKAogICAgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEFpcmNvbmRpdGlvbmluZyBzdGF0dXMiLAogICAgZmlsbCA9ICJBaXJjb25kaXRpb25pbmcgU3RhdHVzIgogICkKCmBgYAoKSGVyZSA2OC40IHBlcmNlbnQgaGFzIGFpcmNvbmRpdGlvbmluZywgYnV0IEkgZG8gbm90IGtub3csIGhvdyBpdCB3aWxsIGFmZmVjdCBwcmVkaWN0aW9ucy4KCgpJIHRoaW5rIHRoYXQgd291bGQgYmUgZW5vdWdoIGV4cGxvcmF0aW9uIGFuZCB3ZSBjYW4gc3RhcnQgd2l0aCBtb2RlbHMuCgoKIyBNb2RlbHMgMSAmIDIKLSBFdmFsdWF0ZSBhbmQgY29tcGFyZSB5b3VyIG1vZGVscyBiYXNlZCBvbiBhIHJlYXNvbmFibGUgZXZhbHVhdGlvbiBtZXRyaWMgb2YgeW91ciBjaG9pY2UuIFlvdSBtdXN0IHVzZSB0aGUgc2FtZSBtZXRyaWMgZm9yIGJvdGggbW9kZWxzLiBSZXBvcnQgYm90aCB0aGUgdHJhaW5pbmcgYW5kIHRoZSBDViBsb3NzLiAKCkZpcnN0LCBJIHdvdWxkIGxpa2UgdG8gc3RhcnQgcHJldHR5IHNpbXBsZSB3aXRoIGxpbmVhciBtb2RlbC4KCkkgY29uc2lkZXIgdG8gdGFrZSBhbGwgdmFyaWFibGVzIHRvIG15IG1vZGVsLCBiZWNhdXNlIHRoZXkgYWxsIHNlZW0gdG8gYmUgdmVyeSBpbXBvcnRhbnQuCgojIyBMaW5lYXIgbW9kZWwKCkkgd2lsbCB1c2UgbG0gZnVuY3Rpb24gaW4gUiB0byBmaW5kIG5lZWRlZCBiZXRhIGNvZWZmaWNpZW50cyBhbmQgY3JlYXRlIG15IG1vZGVsCgpgYGB7cn0KcHJpY2VfbG0gPC0gbG0oZm9ybXVsYSA9IHByaWNlIH4gYXJlYSArIGJlZHJvb21zICsgaG90d2F0ZXJoZWF0aW5nICsgYWlyY29uZGl0aW9uaW5nICsgc3RvcmllcyArIG1haW5yb2FkICsgcGFya2luZyArIGZ1cm5pc2hpbmdzdGF0dXMgKyBiYXRocm9vbXMgKyBndWVzdHJvb20gKyBiYXNlbWVudCArIHByZWZhcmVhLCBkYXRhID0gZHRfaG91c2VzKQoKc3VtbWFyeShwcmljZV9sbSkKYGBgCgpXZSBnb3QgMC42OCBSLXNxdWFyZWQsIHdoaWNoIGlzIG5vdCB0aGF0IGJhZCBmb3IgYSBtb2RlbCBqdXN0IG1hZGUgdXAuIEJ1dCB0aGF0J3Mgbm90IGFsbCwgSSB3aWxsIHRyeSB0byBkbyBiZXR0ZXIgaGVyZSwgYnV0IGZpcnN0LCBhbm90aGVyIG1vZGVsLgoKQnV0IEkgd291bGQgbGlrZSB0byBtZWFzdXJlIHBlcmZvcm1hbmNlIG9mIG15IG1vZGVscyB3aXRoIFJNU0UsIHNvIEkgd2lsbCBjYWxjdWxhdGUgUk1TRSBmb3IgbGluZWFyIG1vZGVsLgoKYGBge3J9CnByaWNlX2xtX3Jtc2UgPC0gbWVhbihzcXJ0KGFicyhwcmljZV9sbSRyZXNpZHVhbHMpKSkKCnByaWNlX2xtX3Jtc2UKYGBgCgojIyBUcmVlIE1vZGVsCgpJIHRoaW5rIHRoaXMgbW9kZWwgY291bGQgcGVyZm9ybSBiZXR0ZXIsIGJlY2F1c2UgdGhlcmUgc29tZSB2YXJpYWJsZXMgd2hpY2ggY2FuIGFmZmVjdCB0aGlzIG1vZGVsIG5vdCBvbmx5IGxpbmVhcmx5LCBidXQgdGhlIG90aGVyIHdheSwgaW4gdGhpcyBjYXNlIHRyZWUgbW9kZWwgY2FuIHNob3cgYmV0dGVyIHBlcmZvcm1hbmNlLgoKSW4gdGhpcyBjb3Vyc2V3b3JrIHdpbGwgYmUgdXNlZCBycGFydCB0byBjcmVhdGUgYSByZWdyZXNzaW9uIHRyZWUuCgpgYGB7cn0KcHJpY2VzX3RyZWUgPC0gcnBhcnQoZGF0YSA9IGR0X2hvdXNlcywgZm9ybXVsYSA9IHByaWNlIH4gYXJlYSArIGJlZHJvb21zICsgaG90d2F0ZXJoZWF0aW5nICsgYWlyY29uZGl0aW9uaW5nICsgc3RvcmllcyArIG1haW5yb2FkICsgcGFya2luZyArIGZ1cm5pc2hpbmdzdGF0dXMgKyBiYXRocm9vbXMgKyBndWVzdHJvb20gKyBiYXNlbWVudCArIHByZWZhcmVhLCBtZXRob2QgPSAnYW5vdmEnKQoKcHJwKHByaWNlc190cmVlLCBkaWdpdHMgPSAtMykKYGBgCgpgYGB7cn0KcHJpbnRjcChwcmljZXNfdHJlZSkKYGBgCgpOb3cgYWZ0ZXIgSSBoYXZlIGJ1aWx0IHdpdGggdGhlIGhlbHAgb2YgcnBhcnQgdHJlZSBtb2RlbCBiYXNlZCBvbiBteSBkYXRhc2V0LCBsZXQgdXMgZXhwbG9yZSBpdDoKCmBgYHtyfQpwcmljZXNfdHJlZQpgYGAKCldlIGNhbiBzZWUsIHRoYXQgd2UgaGF2ZSAzMSBOb2RlcywgSSB0aGluayBmb3IgdGhpcyBraW5kIG9mIGRhdGFzZXQgaXQgbWF5IGJlIG9rYXkuCgpOb3cgaXQgd291bGQgYmUgZ3JlYXQgdG8gcHJ1bmUgdGhlIHRyZWUsIGJlY2F1c2UgSSBkbyBub3Qgd2FudCBteSB0cmVlIHRvIG92ZXJmaXQ6CgpgYGB7cn0KcGxvdGNwKHByaWNlc190cmVlKQpgYGAKVGhpcyBpcyBjb21wbGV4aXR5IG9mIHRoaXMgdHJlZS4gV2UgbmVlZCB0aGUgbG93ZXN0IGNvbXBsZXhpdHksIHRvIGdldCBhcyBmZXcgbGVhZnMgYXMgcG9zc2libGUgdG8gZ2V0IHRoZSBiZXN0IHBlcmZvcm1hbmNlLCBzbyB0aGF0IHRyZWUgd29uJ3Qgb3ZlcmZpdCB0aGUgZGF0YS4KCmBgYHtyfQpwcmljZXNfdHJlZV9taW5fY3AgPC0gcHJpY2VzX3RyZWUkY3B0YWJsZVt3aGljaC5taW4ocHJpY2VzX3RyZWUkY3B0YWJsZVssICJ4ZXJyb3IiXSksICJDUCJdCm1vZGVsX3RyZWUgPC0gcHJ1bmUocHJpY2VzX3RyZWUsIGNwID0gcHJpY2VzX3RyZWVfbWluX2NwICkKcHJwKHByaWNlc190cmVlLGRpZ2l0cyA9IC0zKQpgYGAKCmFmdGVyIHdlIHBydW5lZCB0aGUgdHJlZSwgbGV0J3MgY2FsY3VsYXRlIHRoZSBSTVNFIGZvciB0aGUgdHJlZSBtb2RlbAoKCmBgYHtyfQpwcmljZXNfdHJlZV9wcmVkIDwtIHByZWRpY3QocHJpY2VzX3RyZWUsIGR0X2hvdXNlc1ssIGMoImFyZWEiLCJiYXRocm9vbXMiLCAiYmVkcm9vbXMiLCAiaG90d2F0ZXJoZWF0aW5nIiwgImFpcmNvbmRpdGlvbmluZyIsICJwYXJraW5nIiwgInN0b3JpZXMiLCAibWFpbnJvYWQiLCAiZnVybmlzaGluZ3N0YXR1cyIsICJndWVzdHJvb20iLCAiYmFzZW1lbnQiLCAicHJlZmFyZWEiKV0pCnByaWNlc190cmVlX3Jtc2UgPC0gbWVhbihzcXJ0KGFicyhkdF9ob3VzZXMkcHJpY2UgLSBwcmljZXNfdHJlZV9wcmVkKSkpCgpwcmljZXNfdHJlZV9ybXNlCmBgYAoKIyBFbnNlbWJsZSAKCi0gUmVwZWF0IHRoZSBhbmFseXNpcyBvbmUgZW5zZW1ibGUgbWV0aG9kIG9mIHlvdXIgY2hvaWNlLiAKLSBJbnZlc3RpZ2F0ZSB0aGUgaHlwZXJwYXJhbWV0ZXIgc2V0dGluZ3Mgb2YgeW91ciBlbnNlbWJsZSB3aXRoIHJlZ2FyZHMgdG8geW91ciBldmFsdWF0aW9uIG1ldHJpYy4gCi0gUmVwb3J0IGJvdGggdGhlIHRyYWluaW5nIGFuZCB0aGUgQ1YgbG9zcy4gCi0gU2VsZWN0IHRoZSBiZXN0IGNvbmZpZ3VyYXRpb25zIG9mIHlvdXIgZW5zZW1ibGUgbW9kZWwgYmFzZWQgb24gdGhlIHNhbWUgZXZhbHVhdGlvbiBtZXRyaWMgYXMgYmVmb3JlLgoKIyBOZXVyYWwgTmV0d29yawoKLSBSZXBlYXQgdGhlIGFuYWx5c2lzIHdpdGggYSBuZXVyYWwgbmV0d29yay4gCi0gSW52ZXN0aWdhdGUgdGhyZWUgZGlmZmVyZW50IGNvbmZpZ3VyYXRpb25zIHdpdGggcmVnYXJkcyB0byB5b3VyIGV2YWx1YXRpb24gbWV0cmljIGFuZCBzZWxlY3QgdGhlIGJlc3QgY29uZmlndXJhdGlvbi4gVXNlIHRoZSBzYW1lIGV2YWx1YXRpb24gbWV0cmljIGFzIGJlZm9yZS4KLSBSZXBvcnQgYm90aCB0aGUgdHJhaW5pbmcgYW5kIHRoZSBDViBsb3NzLiAKCiMgTW9kZWwgQ29tcGFyaXNvbgoKLSBDb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgNCBtb2RlbHMuCgojIFBDQQoKLSBSdW4gYSBQQ0Egb24geW91ciBpbnB1dCB2YXJpYWJsZXMgYW5kIGRpc2N1c3MgdGhlIHNjb3BlIGZvciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gaW4geW91ciBkYXRhc2V0LgotIFJlcnVuIHRoZSBwcmV2aW91cyA0IG1vZGVscyBvbiBhbGwgUENzIG9yIG9uIGEgcmVkdWNlZCBudW1iZXIgb2YgUENzLgoKIyMjIENhbGN1bGF0aW5nIFBDcwoKQmVmb3JlIEkgc3RhcnQgd29ya2luZyB3aXRoIFBDQSwgdGhpcyBpcyBpbXBvcnRhbnQgdG8gbm9ybWFsaXplIHRoZSBkYXRhLCBzbyB0aGF0IG1lYXN1cmVtZW50IHNjYWxlIHdpbGwgbm90IGFmZmVjdCBQQ3MuCgpgYGB7cn0KZHRfcGNhIDwtIGRhdGEudGFibGUoc2NhbGUoZHRfaG91c2VzWyxjKCJhcmVhIiwgImJlZHJvb21zIiwgImJhdGhyb29tcyIsICJzdG9yaWVzIiwgInBhcmtpbmciKV0pKQpgYGAKCk5vdyBJIHdhbnQgdG8gcnVuIG15IFBDQSB3aXRoIGhlbHAgb2YgcHJjb21wIGFuZCBnZXQgdGhlIHN1bW1hcnksIHRvIGRpdmUgaW4gdG8gRGF0YS4KCmBgYHtyfQpkdF9ob3VzZXNfcGNhIDwtIHByY29tcChkdF9wY2EpCnN1bW1hcnkoZHRfaG91c2VzX3BjYSkKYGBgClRoYXQgbG9va3MgaW50ZXJlc3RpbmcsIGJ1dCBJIHdvdWxkIGxpa2UgdG8gcGxvdCBpdCwgdG8gdmlzdWFsaXplIGl0LCBzbyB0aGF0IGl0IGNvdWxkIGJlIGVhc2llciB0byB1bmRlcnN0YW5kLgoKYGBge3J9CmZ2aXpfZWlnKGR0X2hvdXNlc19wY2EsIGFkZGxhYmVscyA9IFRSVUUpICsgCiAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCkZyb20gdGhpcyBwbG90IGl0IGlzIG9idmlvdXMgdGhhdCBhbGwgUEMgYXJlIHVzZWZ1bCBhbmQgcmVkdWNpbmcgZGltZW5zaW9uYWxpdHkgd2lsbCBub3QgYmUgYmVuZWZpY2lhbCwgYmVjYXVzZSBsYXN0IDMgUENzIGNvbnRyaWJ1dGUgYXBwcm94LiAxMiUgZWFjaCwgd2hpY2ggaXMgYSBmYWlyIGFtb3VudCBpbiB0aGlzIGNhc2UuIFNvIEkgd291bGQgbGlrZSB0byB1c2UgYWxsIFBDcywgYmVjYXVzZSBpdCBpcyBub3QgYWx3YXlzIG5lY2Vzc2FyeSB0byBjdXQgZGltZW5zaW9uYWxpdHkgYW5kIGl0IGRlcGVuZHMgb24gaG93IG11Y2ggdmFyaWFibHNlIHdlIGFjdHVhbGx5IGhhdmUsIGFuZCBob3cgYmlnIG9mIGEgY29udHJpYnV0aW9uIG1ha2VzIGVhY2ggUEMuIE1heSBiZSBpbiB0aGlzIGNhc2UgImNoYW5nZSBvZiBiYXNpcyIgd2lsbCBwZXJmb3JtIGJldHRlci4KCiMjIyBSdW5uaW5nIG1vZGVscyB3aXRoIFBDcwoKTm93IGFzIEkgaGF2ZSBjYWxjdWxhdGVkIFBDcywgaXQgaXMgdGltZSB0byBydW4gbW9kZWxzIHdpdGggbmV3IGlucHV0cy4KCiMjIyMgTGluZWFyIG1vZGVsCgpGaXJzdCB3ZSB3aWxsIHN0YXJ0IG9mZiB3aXRoIGxpbmVhciBtb2RlbC4KCmBgYHtyfQpwY190YWJsZSA8LSBkdF9ob3VzZXNfcGNhJHgKCmR0X2hvdXNlc19wY190YWJsZV9mb3JfbG0gPC0gZGF0YS5mcmFtZShwcmljZSA9IGR0X2hvdXNlcyRwcmljZSwgcGNfdGFibGUpCgpwcmljZV9sbV9wY2EgPC0gbG0oZm9ybXVsYSA9IHByaWNlIH4gLiwgZGF0YSA9IGR0X2hvdXNlc19wY190YWJsZV9mb3JfbG0pCgpzdW1tYXJ5KHByaWNlX2xtX3BjYSkKYGBgCgpgYGB7cn0KcHJpY2VfbG1fcGNhX3Jtc2UgPC0gbWVhbihzcXJ0KGFicyhwcmljZV9sbV9wY2EkcmVzaWR1YWxzKSkpCgpwcmljZV9sbV9wY2Ffcm1zZQpgYGAKCgpOb3csIGFmdGVyIFBDQSB0aGUgcmVzdWx0cyBhcmUgd29yc2UgYW5kIEkgdGhpbmsgdGhpcyBpcyBiZWNhdXNlIHRoZXJlIGFyZSBmYWlyIGFtb3VudCBvZiBiaW5hcnkgdmFyaWFibGVzIGluIHRoaXMgZGF0YXNldCBhbmQgUENBIGNvdWxkIG5vdCBjYXB0dXJlIGFsbCBpbmZvcm1hdGlvbiwgZXNwZXNpYWxseSBpbmZvcm1hdGlvbiwgd2hpY2ggaXMgY2FwdHVyZWQgaW4gYmluYXJ5IHZhcmlhYmxlcywgc3VjaCBhcyAibWFpbnJvYWQiLCAiYWlyY29uZGl0aW9uaW5nIiBhbmQgc28gb24uCgogCiMgTW9kZWwgU2VsZWN0aW9uCgotIENvbXBhcmUgYWxsIG9mIHlvdXIgbW9kZWxzIGJhc2VkIG9uIHRoZWlyIENWIHBlcmZvcm1hbmNlLiBZb3Ugd2lsbCBoYXZlIDggb3B0aW9ucyB0byBjb25zaWRlcjogZm91ciBtb2RlbHMgd2l0aCBhbmQgd2l0aG91dCBQQ0EuCi0gUHJlc2VudCB0aGUgcmVzdWx0cyBpbiBhIHRhYmxlIG9yIGNoYXJ0LgotIEVzdGltYXRlIHRoZSBleHBlY3RlZCBsb3NzIG9mIHlvdXIgYmVzdCBtb2RlbCBvbiB0aGUgdGVzdCBzZXQuCgoqKioKCgoKCgoKCgo=